package cn.tedu.charging.cost.service.impl;

import cn.tedu.charging.common.pojo.param.ProgressCostParam;
import cn.tedu.charging.common.pojo.po.ChargingCostRulePO;
import cn.tedu.charging.common.pojo.vo.ProgressCostVO;
import cn.tedu.charging.cost.mapper.CostRuleMapper;
import cn.tedu.charging.cost.service.CostService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;

@Service
@Slf4j
public class CostServiceImpl implements CostService {
    @Override
    public ProgressCostVO calculateCost(ProgressCostParam costParam) {
        //充电度数 * 电价  = 花费
        //充电度数 是设备同步过来的
        //电价 是数据库中配置的
        //计价规则
        /**
         * 名称,   开始时间 结束时间 价格  枪类型  站点id
         * 尖上午     8     12    1.50   快      5
         * 尖下午,   14     18    1.50   快      5
         * 峰,      18     24    1.40   快      5
         * 平,      12     14    1.30   快      5
         * 谷,      0      8     1.00   快      5
         * 尖上午    8     12     1.80   快      6
         */
        //获取当前时间 去计价规则表中查询 不同时间段对应的计价规则
        //获取当前时间
        int hour = LocalDateTime.now().getHour();
        //通过时间和场站id 匹配计价规则
        ChargingCostRulePO costRulePO = getChargingCostRuleByStationId(costParam.getStationId(), hour);
        log.debug("当前时间{},匹配的计价规则：{}", hour, costRulePO);
        if (costRulePO == null) {
            log.debug("没有匹配的计价规则,警告,通知管理员处理");
            //或者使用一个默认的价格规则 1.4
        }
        //获取电价
        BigDecimal powerFee = costRulePO.getPowerFee();
        //计算当前同步的订单的计费信息
        log.debug("当前时间{},同步的充电的度数是{},每度电的价格:{}", hour, costParam.getChargingCapacity(), powerFee);
        BigDecimal onceCost = calculateOnceCost(costParam.getChargingCapacity(), powerFee);
        log.debug("当前时间{},同步的充电的度数是{},每度电的价格:{},订单的计费信息是:{}", hour, costParam.getChargingCapacity(), powerFee, onceCost);

        //计算总价 totalCost
        BigDecimal totalCost = getTotalCost(costParam.getOrderNo(), onceCost);
        //计算时长 已充电多长时间 当前时间 - 第一次同步时间
        Long totalTime = getTotalTime(costParam.getOrderNo());

        //定义返回值
        ProgressCostVO vo = new ProgressCostVO();
        //总花费
        vo.setTotalCost(totalCost);
        //设置每度电的价格 如果跨时间 按照时间区间计算价格
        vo.setPowerFee(powerFee);
        //总时长
        vo.setTotalTime(totalTime);
        //总度数
        //*****************同步策略 不包含 同步的是 实际充电度数********************
        // 简单 考虑数据丢失的影响  总度数 需要参考 价格进行 累加
        //  同步次数  实际充电度数 同步度数   总充电度数
        //    1         1         1         1
        //    2         10       10         11   丢了总度 不是 22 1 + 11 = 12 度 少了 10度
        //    3         11       11         22
        //*****************同步策略 包含(包含了原来同步的度数) 同步的是 实际充电度数 ********************
        //   每次同步的都是 总度数 实际度数 需要 用户当前同步 减去 上次同步的度数
        //    1         1         1         1
        //    2         10        11        11 丢了 下次同步 会保护丢失的10度
        //    3         11        22        22
        // 如果每次同步的的是 实际的充电度数 总度数 和 价格一样 累加计算
        //
        //vo.setTotalCapacity();
        //返回结果
        return vo;
    }

    /**
     * 通过订单号 获取已经同步的时长
     * 当前时间 减去 开始时间
     *
     * @param orderNo
     * @return
     */
    private Long getTotalTime(String orderNo) {
        //定义时间计算的 Redis 的 key 按照订单号 区别
        String key = "charging_cost_time_" + orderNo;
        //操作字符串类型
        ValueOperations<String, Long> valueOperations = redisTemplate.opsForValue();
        //获取当前订单号的开始时间
        Long startTime = valueOperations.get(key);
        //判断时间是否为空
        if (startTime == null) {
            //说明是第一次同步
            //记录第一次同步的时间
            startTime = System.currentTimeMillis();
            //保存开始时间到Redis
            valueOperations.set(key, startTime);
        }
        //不是第一次 同步
        Long now = System.currentTimeMillis();
        //用 当前时间 - 开始时间
        Long costTime = now - startTime;
        return costTime;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 计算订单的 总价
     *
     * @param orderNo
     * @param onceCost
     * @return
     */
    private BigDecimal getTotalCost(String orderNo, BigDecimal onceCost) {
        //定义花费累加的 Redis 的 key 按照订单号 区别
        String key = "charging_cost_" + orderNo;
        //操作字符串类型
        ValueOperations<String, BigDecimal> valueOperations = redisTemplate.opsForValue();
        //累加
        Double increment = valueOperations.increment(key, onceCost.doubleValue());
        return new BigDecimal(increment).setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal calculateOnceCost(BigDecimal chargingCapacity, BigDecimal powerFee) {
        //BigDecimal 方便计算 提供了一些方法
        // add:加 subtract:减 multiply:乘 divide:除
        return powerFee
                .multiply(chargingCapacity)
                .setScale(2, RoundingMode.HALF_UP);
    }

    @Autowired
    private CostRuleMapper costRuleMapper;

    private ChargingCostRulePO getChargingCostRuleByStationId(Integer stationId, int hour) {
        //通过场站id 获取所有计价规则
        long start = System.currentTimeMillis();
        log.debug("查询计价规则,开始时间:{}",start);
        List<ChargingCostRulePO> costRules = getCostRulePOS(stationId);
        log.debug("查询计价规则,耗时:{}",System.currentTimeMillis() - start);
        // 循环计价规则列表
        for (ChargingCostRulePO costRulePO : costRules) {
            if (hour >= costRulePO.getStartTime() && hour < costRulePO.getEndTime()) {
                return costRulePO;
            }
        }
        return null;
    }

    /**
     * 通过场站id 获取所有计价规则
     * <p>
     * //方案1 sql 不推荐
     * //QueryWrapper queryWrapper = new QueryWrapper();
     * //等于 =
     * //queryWrapper.eq("station_id",stationId);
     * // start_time 开始时间 8  end_time 结束时间  12  now 当前时间 10
     * <p>
     * // 8 <= 10 <= 12 错误的
     * <p>
     * // 8 <= 10 < 12
     * // 8 <= 8 < 12
     * <p>
     * // 8 < 10 <= 12
     * // 8 < 12 <= 12
     * <p>
     * // queryWrapper lt ge eq le gt 自己了解API
     * <p>
     * <p>
     *
     * //方案2 查询当前站 stationId 所有的计价规则
     *
     * //方案3 缓存 计价规则 推荐
     * // 旁路缓存
     * // 1 先从缓存中取
     * // 2 如果缓存中没有数据
     * // 3 去数据库查
     * // 4 把数据库的数据 放入缓存
     * // 缓存预热 参考 附近充电站 预热 充电站的逻辑
     *
     * @param stationId
     * @return
     */
    private List<ChargingCostRulePO> getCostRulePOS(Integer stationId) {
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json());
        //1 先从缓存中获取
        //定义操作Redis的字符串 redisTemplate.opsForValue()
        ValueOperations<String, List<ChargingCostRulePO>> valueOperations =
                redisTemplate.opsForValue();
        RedisSerializer keySerializer = redisTemplate.getKeySerializer();
        log.debug("keySerializer:{}", keySerializer);
        //定义缓存的key
        String key = "charging_cost_rule_" + stationId;
        List<ChargingCostRulePO> cacheCostRules = valueOperations.get(key);
        if (cacheCostRules != null) {
            log.debug("缓存命中,缓存中有数据,直接返回");
            return cacheCostRules;
        }else{
            log.debug("缓存未命中,从数据库中获取,并放入缓存");
            //数据库中获取 查询当前站 stationId 所有的计价规则
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq("station_id", stationId);
            //计价规则列表
            List<ChargingCostRulePO> costRules =
                    costRuleMapper.selectList(queryWrapper);
            if (!CollectionUtils.isEmpty(costRules)) {
                //数据库不空 写入缓存
                valueOperations.set(key, costRules);
                return costRules;
            } else {
                log.error("数据库中没有该站点:{}的计价规则", stationId);
            }
        }
        return null;
    }
}

